#include "LinuxHardwareInfo.h"

LinuxHardwareInfo::LinuxHardwareInfo()
    : cpu_file_("/proc/cpuinfo")
    , disk_file_("/proc/diskstats")
    , bios_dir_("/sys/class/dmi/id/")
    , bios_files_name_({"bios_vendor","bios_version","bios_date"})
    , dmidecode_command_path_({"/usr/sbin/dmidecode", "/sbin/dmidecode", "/usr/local/sbin/dmidecode"})
    , board_dir_("/sys/class/dmi/id/")
    , board_files_name_({"board_vendor","board_version","board_name","board_asset_tag"})
{
}

LinuxHardwareInfo::~LinuxHardwareInfo()
{
}

void LinuxHardwareInfo::GetProcessorAllInfo(std::vector<CPUInfo> &_cpu_info)
{
    std::ifstream file(cpu_file_);
    
    if (!file.is_open())
    {
        std::cerr << "CPU文件: " << cpu_file_ << "打开失败" << std::endl;

        return;
    }

    CPUInfo cpuInfo;
    std::string line;
    while (std::getline(file, line))
    {
        if (line.empty())
        {
            // 读取下一个cpu的信息
            if(!cpuInfo.model_name_.empty()) 
            {
                _cpu_info.push_back(cpuInfo);
                cpuInfo = CPUInfo();
            }
        }
        else
            ParseProcessorLine(line, cpuInfo);
    }

    // 添加最后一个CPU信息
    if (!cpuInfo.model_name_.empty()) 
    {
        _cpu_info.push_back(cpuInfo);
    }

    file.close();
}

void LinuxHardwareInfo::GetDiskStatsAllInfo(std::vector<DiskStatsInfo> &_disk_infos)
{
    std::ifstream disk_stats_file(disk_file_);
    if (!disk_stats_file.is_open()) 
    {
        std::cerr << "磁盘文件: " << disk_file_ << "打开失败" << std::endl;
     
        return;
    }

    std::string line;
    while (std::getline(disk_stats_file, line))
    {
        std::istringstream iss(line);

        DiskStatsInfo diskStats;

        std::string inFlight, ioTicks, timeInQueue;

        if (!(iss >> diskStats.major_device_num_ >> diskStats.minor_device_num_ >> diskStats.device_name_
                  >> diskStats.read_completed_ >> diskStats.read_merged_ >> diskStats.read_sectors_
                  >> diskStats.read_time_spent_ >> diskStats.write_completed_ 
                  >> diskStats.write_merged_ >> diskStats.write_sectors_ >> diskStats.write_time_spent_ 
                  >> inFlight >> ioTicks >> timeInQueue)) 
        {
            continue;
        }

        _disk_infos.push_back(diskStats);
    }

    disk_stats_file.close();

    return;
}

bool LinuxHardwareInfo::GetBIOSAllInfo(BIOSMoreInfo &_bios_info)
{
    IsUseDmidecodeCommand()

    if(is_use_dmidecode)   // 使用dmidecode命令获取更详细的BISO信息
    {
        try
        {
            // 对获取到的命令输出进行字符串切割
            std::vector<std::string> bios_info_vec = SplitByNewline(GetStrByCommand("dmidecode -t bios"));

            for(const auto& line : bios_info_vec)
            {
                ProcessBIOSLine(line, _bios_info);
            }

        } catch (const std::exception& e)
        {
            std::cerr << "dmidecode命令执行错误: " << e.what() << std::endl;

            // 抛出异常后只获取基本信息
            GetBIOSBasicInfo(_bios_info);

            return false;
        }
    }
    else    // 只获取基本信息
        GetBIOSBasicInfo(_bios_info);

    return is_use_dmidecode;
}

bool LinuxHardwareInfo::GetBoardAllInfo(BaseboardMoreInfo &_board_info)
{
    IsUseDmidecodeCommand()

    if(is_use_dmidecode)   // 使用dmidecode命令获取更详细的主板信息
    {
        try
        {
            // 对获取到的命令输出进行字符串切割
            std::vector<std::string> board_info_vec = SplitByNewline(GetStrByCommand("dmidecode -t baseboard"));

            bool is_parse = false;
            for(const auto& line : board_info_vec)
            {
                if(line == "Base Board Information")
                    is_parse = true;
                else if(line.empty())
                    is_parse = false;
                
                if(is_parse)
                    ProcessBoardLine(line, _board_info);
            }

        } catch (const std::exception& e)
        {
            std::cerr << "dmidecode命令执行错误: " << e.what() << std::endl;

            // 抛出异常后只获取基本信息
            GetBoardBasicInfo(_board_info);

            return false;
        }
    }
    else    // 只获取基本信息
        GetBoardBasicInfo(_board_info);

    return is_use_dmidecode;
}

void LinuxHardwareInfo::GetBIOSBasicInfo(BIOSMoreInfo &_bios_info)
{
    for(const auto& file_name : bios_files_name_)
    {
        // bios文件路径
        std::string path = bios_dir_ + file_name;
        std::ifstream file(path);
        if (!file.is_open())
        {
            std::cerr << "BIOS文件: " << path << "打开失败" << std::endl;
            return;
        }

        std::string value;
        std::getline(file, value);
        
        UpdateBIOSInfo(_bios_info,file_name,value);
    }
}

void LinuxHardwareInfo::GetBoardBasicInfo(BaseboardMoreInfo &_board_info)
{
    for(const auto& file_name : board_files_name_)
    {
        // board文件路径
        std::string path = board_dir_ + file_name;
        std::ifstream file(path);
        if (!file.is_open())
        {
            std::cerr << "主板文件: " << path << "打开失败" << std::endl;
            return;
        }

        std::string value;
        std::getline(file, value);
        
        UpdateBoardInfo(_board_info,file_name,value);
    }
}

void LinuxHardwareInfo::RemoveWhiteSpace(std::string &_str)
{
    int head_idx = 0, tail_idx = _str.length() - 1;

    bool head_flag = false, tail_flag = false;

    while (head_idx != tail_idx)
    {
        // 使用std::isspace方法判断空白字符，因为会有\t等情况
        if(!head_flag && std::isspace(_str[head_idx]))
            head_idx++;
        else
            head_flag = true;

        if(!tail_flag && std::isspace(_str[tail_idx]))
            tail_idx--;
        else
            tail_flag = true;

        if(head_flag && tail_flag)
            break;
    }

    _str = _str.substr(head_idx, tail_idx - head_idx + 1);

    return;
}

void LinuxHardwareInfo::ParseProcessorLine(const std::string &_line, CPUInfo &_cpu_info)
{
    SplitRemoveWhite(_line)

    UpdateCpuInfo(_cpu_info,key,value);
}

void LinuxHardwareInfo::ProcessBIOSLine(const std::string &_line, BIOSMoreInfo &_bios_info)
{
    SplitRemoveWhite(_line)

    UpdateBIOSInfo(_bios_info,key,value);
}

void LinuxHardwareInfo::ProcessBoardLine(const std::string &_line, BaseboardMoreInfo &_board_info)
{
    SplitRemoveWhite(_line)

    UpdateBoardInfo(_board_info,key,value);
}

bool LinuxHardwareInfo::FindDmidecodePath()
{
    for (const auto& path : dmidecode_command_path_) 
    {
        if (access(path.c_str(), X_OK) == 0) 
        {
            return true; // 找到有效路径
        }
    }

    return false;
}

std::string LinuxHardwareInfo::GetStrByCommand(const char *_cmd)
{
    std::array<char, 128> buffer;
    std::string result;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(_cmd, "r"), pclose);

    if (!pipe) 
    {
        throw std::runtime_error("Failed to execute command");
    }

    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) 
    {
        result += buffer.data();
    }

    return result;
}

std::vector<std::string> LinuxHardwareInfo::SplitByNewline(const std::string &_str)
{
    std::vector<std::string> lines;
    std::istringstream iss(_str);
    std::string line;   
    
    while (std::getline(iss, line, '\n')) 
    {
        lines.push_back(line);
    }

    return lines;
}

void LinuxHardwareInfo::UpdateBoardInfo(BaseboardMoreInfo &_board_info, const std::string &_key, const std::string &_value)
{
    static const std::unordered_map<std::string, BoardMemberPtr> keys_map = {
        {"Manufacturer",   &BaseboardMoreInfo::vendor_},
        {"Product Name",   &BaseboardMoreInfo::name_},
        {"Version",  &BaseboardMoreInfo::version_},
        {"Serial Number",       &BaseboardMoreInfo::serial_num_},
        {"Asset Tag",  &BaseboardMoreInfo::asset_tag_},
        {"Location In Chassis",    &BaseboardMoreInfo::location_in_chassis_},
        {"Type",    &BaseboardMoreInfo::type_},
        {"board_vendor",   &BaseboardMoreInfo::vendor_},
        {"board_name",   &BaseboardMoreInfo::name_},
        {"board_version",   &BaseboardMoreInfo::version_},
        {"board_asset_tag",   &BaseboardMoreInfo::asset_tag_},
        {"board_serial",   &BaseboardMoreInfo::serial_num_}
    };

    if (auto it = keys_map.find(_key); it != keys_map.end()) 
    {
        _board_info.*(it->second) = _value;
    }
    // else
    //     std::cerr << "解析出" << _key << "和" << _value << std::endl;
}

void LinuxHardwareInfo::UpdateBIOSInfo(BIOSMoreInfo &_bios_info, const std::string &_key, const std::string &_value)
{
    static const std::unordered_map<std::string, BIOSMemberPtr> keys_map = {
        {"Vendor",   &BIOSMoreInfo::vendor_},
        {"Release Date",   &BIOSMoreInfo::date_},
        {"Version",  &BIOSMoreInfo::version_},
        {"Address",       &BIOSMoreInfo::address_},
        {"Runtime Size",  &BIOSMoreInfo::runtime_size_},
        {"ROM Size",    &BIOSMoreInfo::rom_size_},
        {"BIOS Revision",    &BIOSMoreInfo::bios_revision_},
        {"Firmware Revision",    &BIOSMoreInfo::firmware_revisioin_},
        {"bios_vendor",   &BIOSMoreInfo::vendor_},
        {"bios_date",   &BIOSMoreInfo::date_},
        {"bios_version",   &BIOSMoreInfo::version_}
    };

    if (auto it = keys_map.find(_key); it != keys_map.end()) 
    {
        // 部分字段带有单位，去除单位
        if(_value.find(" ") != std::string::npos)
        {
            size_t pos = _value.find(" ");

            _bios_info.*(it->second) = _value.substr(0, pos);
        }
        else
            _bios_info.*(it->second) = _value;
    }
    // else
    //     std::cerr << "解析出" << _key << "和" << _value << std::endl;
}

void LinuxHardwareInfo::UpdateCpuInfo(CPUInfo& _cpu_info, const std::string &_key, const std::string &_value)
{
    static const std::unordered_map<std::string, CpuMemberPtr> keys_map = {
        {"processor",   &CPUInfo::processor_},
        {"vendor_id",   &CPUInfo::vendor_id_},
        {"cpu family",  &CPUInfo::cpu_family_},
        {"model",       &CPUInfo::model_},
        {"model name",  &CPUInfo::model_name_},
        {"stepping",    &CPUInfo::stepping_},
        {"microcode",    &CPUInfo::microcode_},
        {"cpu MHz",    &CPUInfo::cpu_freq_},
        {"physical id",   &CPUInfo::physical_id_},
        {"siblings",   &CPUInfo::siblings_},
        {"core id",   &CPUInfo::core_id_},
        {"cpu cores",   &CPUInfo::cpu_cores_},
        {"clflush size",   &CPUInfo::clflush_size_}
    };

    if (auto it = keys_map.find(_key); it != keys_map.end()) 
    {
        // 部分字段带有单位，去除单位
        if(_value.find(" ") != std::string::npos)
        {
            size_t pos = _value.find(" ");

            _cpu_info.*(it->second) = _value.substr(0, pos);
        }
        else
            _cpu_info.*(it->second) = _value;
    }
    // else
    //     std::cerr << "解析出" << _key << "和" << _value << std::endl;
}
